/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import * as fs from 'fs';
import path from 'path';

import { cloneDeep } from '@baize/wdk';
import { FileSet, hvigorCore, Json5Reader, TaskDetails, TaskInputValue } from '@ohos/hvigor';
import { getUrl, URL_KEY_ENUM } from '@ohos/hvigor-logger';
import { getOsLanguage, isSubPath } from '@ohos/hvigor-common';
import { JsonReader } from '@ohos/hvigor/src/base/util/json-reader';
import { nodeConfigPathInfo } from '@ohos/hvigor/src/common/node-config-path-info';
import fse from 'fs-extra';

import { buildOptionPath } from '../../common/build-option-path-info.js';
import { isSingleFramework, parsingProfileName } from '../../common/common-util.js';
import { projectOhosConfigManager } from '../../common/global/project-ohos-config-manager.js';
import { BuildDirConst } from '../../const/build-directory-const.js';
import {
  BundleType,
  CommonConst,
  CustomTypesConst,
  DefaultTargetConst,
  HvigorConfigConst
} from '../../const/common-const.js';
import { DeviceTypeConst } from '../../const/device-type-const.js';
import { ApiVersion } from '../../const/sdk-const.js';
import { CodeType } from '../../enum/code-type-enum.js';
import { ModuleType } from '../../enum/module-type-enum.js';
import { RuntimeTypeEnum } from '../../enum/runtime-type-enum.js';
import { JsonProfile } from '../../model/json-profile.js';
import { AppOpt } from '../../options/build/app-ohos-config.js';
import { ConfigJson } from '../../options/configure/config-json-options.js';
import { ModuleJson } from '../../options/configure/module-json-options.js';
import { ApiType } from '../../project/data/hap-extra-info.js';
import { ProjectExtraInfoService } from '../../project/project-extra-info-service.js';
import { FileUtil } from '../../utils/file-util.js';
import { InjectUtil } from '../../utils/inject-util.js';
import { getJson5Obj } from '../../utils/json-util.js';
import { ohPackageLoader } from '../../utils/loader/file/oh-package-loader.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ValidateUtil, VersionCheckRes } from '../../utils/validate/validate-util.js';
import { INPUT_KEY } from '../abstract-merge-profile.js';
import { HarExtendInfo } from '../har/har-extend-info.js';
import { TargetTaskService } from '../service/target-task-service.js';
import { OhosHapTask } from '../task/ohos-hap-task.js';
import { PackageJsonOpt } from '../task/ohos-har-task.js';
import { getCustomTypePathIfExists } from '../../utils/build-profile-utils.js';

import RequestPermissionObj = ModuleJson.RequestPermissionObj;
import ModuleOptObj = ModuleJson.ModuleOptObj;
import ConfigOptObj = ConfigJson.ConfigOptObj;
import ReqPermissionsObj = ConfigJson.ReqPermissionsObj;
import MetadataObj = ModuleJson.MetadataObj;

import { findTargetProduct } from '../../common/find-target-product.js';
import { GlobalProjectDataService } from '../service/global-project-data-service.js';
/**
 * preBuild Task
 *
 * @since 2022/1/10
 */
export abstract class AbstractPreBuild extends OhosHapTask {
  private _log = OhosLogger.getLogger(AbstractPreBuild.name);
  private readonly entryCardPath: string;

  // module相关属性
  protected readonly apiType: string;
  protected readonly targetStatusCode: number;
  protected readonly syscapJsonPath: string;
  protected readonly containsEts: boolean;
  protected readonly deviceTypes: string[];
  protected readonly dependencies: Record<string, string>;
  protected readonly compilePluginFile: string | undefined;
  protected readonly dynamicDependencies: Record<string, string>;
  protected readonly jsonProfile: JsonProfile;
  protected readonly fromConfigFilePathArr: string[];
  protected readonly projectProfilePath: string;
  protected readonly profilePath: string;

  declareInputs(): Map<string, TaskInputValue> {
    const sdkToolchainsComponentVersion = this.sdkInfo.getToolchainsComponentVersion();
    const map = new Map<string, TaskInputValue>();
    map
      .set('compileApiVersion', this.compileApiVersion)
      .set('compatibleApiVersion', this.compatibleApiVersion)
      .set('targetStatusCode', this.targetStatusCode)
      .set('apiType', this.apiType)
      .set('deviceType', this.deviceTypes)
      .set('codeType', this.containsEts)
      .set('sdkToolchainsComponentVersion', sdkToolchainsComponentVersion!)
      .set('profileModuleName', this.moduleName)
      .set(INPUT_KEY.moduleJsonOpt, JSON.stringify(this.jsonProfile))
      .set('isSupportOhpmProj', this.supportOHPM())
      .set('removePermissions', JSON.stringify(this.targetService.getBuildOption()?.removePermissions));
    if (this.compilePluginFile !== undefined) {
      map.set('compilePluginFile', this.compilePluginFile);
    }

    const customTypes = this.targetService.getCustomTypes();
    map.set('customTypes', customTypes ?? '');
    if (customTypes && Array.isArray(customTypes) && customTypes.length > 0) {
      for (const type of customTypes) {
        // 包名跳过
        if (this.dependencies[type] || this.dependencies[`@types/${type}`]) {
          continue;
        }
        const filePath = getCustomTypePathIfExists(this.moduleModel.getProjectDir(), type);
        map.set(type, !!filePath);
      }
    }
    return map;
  }

  declareInputFiles(): FileSet {
    const inputFiles: FileSet = super.declareInputFiles();
    if (fse.existsSync(this.jsonProfile.jsonFilePath)) {
      inputFiles.addEntry(this.jsonProfile.jsonFilePath);
    }

    this.fromConfigFilePathArr.forEach((filePath) => {
      inputFiles.addEntry(filePath);
    });
    return inputFiles;
  }

  async beforeAlwaysAction() {
    await super.beforeAlwaysAction();

    // 检查stageMode工程entryModules字段存在则抛出warning
    this.checkEntryModules();
    this.checkEmptyProperty();
    this.checkEmptyTargetSdkVersion();
  }

  private checkEmptyTargetSdkVersion() {
    const instance = GlobalProjectDataService.getInstance();
    // 已经配置了targetSdkVersion或者已经输出过warning，不再输出
    if (typeof this.targetData.getTargetSdkVersion() !== 'undefined' || instance.getIsEmptyTargetSdkVersionCheck()) {
      return;
    }
    instance.setIsEmptyTargetSdkVersionCheck();
    const country = getOsLanguage();
    const host = getUrl(URL_KEY_ENUM.DEVELOPER_URL);
    this._log.warn(`The project has not explicitly set the 'targetSdkVersion' version at build-profile.json5. It is recommended to configure it. Reference: ${host}/consumer/${country}/doc/harmonyos-guides/ide-hvigor-build-profile-app#section45865492619`)
  }

  protected constructor(taskService: TargetTaskService, taskName: TaskDetails) {
    super(taskService, taskName);
    this.apiType = this.moduleModel.getApiType();
    this.targetStatusCode = this.targetService.getTargetData().getTargetStatus().getStatusCode();
    this.syscapJsonPath = path.resolve(this.moduleModel?.getSourceRootByTargetName(this.targetName), CommonConst.SYSCAP_JSON);
    this.containsEts = this.moduleModel.getSourceSetByTargetName(this.targetData.getTargetName()).getCodeMap().has(CodeType.ETS);
    this.deviceTypes = this.targetData.getTargetDeviceType();
    this.dependencies = this.getDependencies(this.moduleModel, true);
    this.compilePluginFile = this.targetService.getBuildOption().arkOptions?.compilePluginFile;
    this.dynamicDependencies = this.getModuleDynamicDependencies();
    this.jsonProfile = this.moduleModel.getJsonProfileByModel(this.service, this.targetName);
    this.fromConfigFilePathArr = this.getFromConfigFilePathArr();
    this.entryCardPath = path.resolve(this.moduleModel.getParentProject().getProjectDir(), 'EntryCard');
    this.projectProfilePath = this.projectModel.getProfilePath();
    this.profilePath = this.moduleModel.getProfilePath();
  }

  initTaskDepends() {
    // no depends
  }

  protected async doTaskAction(): Promise<void> {
    // 检查对工程外模块的引用
    this.checkOutModule();

    // 校验是否是api8及以上瘦设备工程并且正在构建测试包
    if (this.isPackingTestPackage()) {
      if (isSingleFramework()) {
        throw new Error('API version 9 or later and Lite-Device projects do not support compilation test packages.');
      } else {
        throw new Error('API version 8 or later and Lite-Device projects do not support compilation test packages.');
      }
    }

    // 校验har和hsp模块的版本号和名称是否符合规范
    this.ohPackageCheck();

    // 校验compileSdkVersion 和 apiType
    this.versionCheck();

    // 校验API8的wearable、lite-wearable设备的语言
    this.checkCodeTypeForWearable();

    // 校验API10 lite-wearable设备的语言
    this.checkCodeTypeForLiteWearable();

    // 校验ohpm的SDK版本是否支持
    this.checkOhpmProjectSdkVersion();

    // 根据不同的模型进行的不同的校验
    this.doValidateForDiffApiType();

    // 根据ArkTS1.2的规格进行校验(只在ArkTS1.2下才会执行)
    this.doValidateForStaticArkTS();

    // 校验不同API是否支持单元测试
    this.checkApiVersionForUnitTest();

    // 校验权限请求配置
    this.checkRequestPermissions();

    // 校验权限删除配置
    this.checkRemoveRequestPermissions();

    // 校验atomicService
    this.validateAtomicService();

    // 校验卡片配置
    this.checkWidget();

    this._log._printDebugCommand(`${process.platform}: JAVA_HOME, CLASSPATH`, [{ JAVA_HOME: process.env.JAVA_HOME }, { CLASSPATH: process.env.CLASSPATH }]);
    this._log._printDebugCommand(`${process.platform}: NODE_HOME`, [{ NODE_HOME: process.env.NODE_HOME }]);

    // 检查是否支持自定义types
    this.checkCustomTypesSupport();

    // 校验用户配置的compilePluginFile是否符合规范
    this.checkHvigorPluginFile();

    // 检查是否支持dynamicDependencies
    this.checkDynamicDependenciesSupport();

    // 检查用户配置的workerConfig是否符合规范
    this.checkWorkerConfig();

    // 检查差异化的sourceRoot
    this.checkExtendSourceDirs();

    // 检查oh_modules文件main字段配置的文件是否存在
    await this.checkPackageJson5Config();

    // 检查要编译的模块是否有指定的deviceTypes
    this.checkDeviceTypes();

    // 校验路径及文件名包含中文则报错
    this.checkPathChinese();

    // 字节码har校验
    this.checkByteCodeHar();

    // 检查打hap的模块级配置中是否配置了excludeSoFromInterfaceHar, 有则报错
    this.checkExcludeSoFromInterfaceHar();

    // check fastBuildApp on FA Mode
    this.validateFastBuildAppOnFAMode();
  }

  /**
   * 检查工程外的模块是否满足：
   * 1. FA主工程不能引用工程外的的模块（无论什么模型）
   * 2. Stage主工程不能引用FA模块
   * 3. 工程外的hap不支持被引用
   * @private
   */
  private checkOutModule() {
    const isFa = this.projectModel.isFaMode();
    const isOutModule = this.moduleModel.isOutModule();
    isFa && isOutModule && this._log.printErrorExit('THIS_PROJECT_IS_IN_THE_FA_MODEL_AND_DOES_NOT_ALLOW_FOR_EXTERNAL_DEPENDENCIES');
    !isFa &&
      this.moduleModel.getApiType() === ApiType.FA &&
      this._log.printErrorExit('THIS_PROJECT_IS_IN_THE_STAGE_MODEL_AND_DOES_NOT_ALLOW_FOR_DEPENDENCIES_OF_MODULES_INTHE_FA_MODEL');
    isOutModule && this.moduleModel.isHapModule() && this._log.printErrorExit('CANNOT_DEPEND_ON_HAP_MODULES_OUTSIDE_OF_THIS_PROJECT');
  }

  /**
   * 校验路径及文件名包含中文则报错
   * @private
   */
  private checkPathChinese(): void {
    const moduleModelNodePaths = this.moduleModelNodePaths;
    moduleModelNodePaths.forEach((nodePath) => {
      if (/[\u4e00-\u9fff]/.test(nodePath)) {
        // 杀死编译进程
        this._log.printErrorExit('PATH_CAN_NOT_CONTAIN_CHINESE_CHARACTERS', [nodePath]);
      }
    });
  }

  /**
   * 检查要编译的模块是否具有命令行指定的deviceTypes, har模块不做设备校验
   * @private
   */
  private checkDeviceTypes() {
    const mustType = hvigorCore.getExtraConfig().get(CommonConst.REQUIRED_DEVICE_TYPE);
    if (mustType === undefined || this.moduleModel.isHarModule()) {
      return;
    }
    const deviceTypes = this.moduleModel.getDeviceTypes();

    // 用一个map承载一个设备类型和什么设备类型相同，phone和default相同，tablet和2in1相同
    const deviceSomeMap = new Map<string, string[]>([
      ['phone', ['phone', 'default']],
      ['default', ['phone', 'default']],
      ['tablet', ['tablet', '2in1']],
      ['2in1', ['tablet', '2in1']],
    ]);

    const mustTypeGroup = deviceSomeMap.get(mustType) || [mustType];
    if (mustTypeGroup.some((type) => deviceTypes.includes(type))) {
      return;
    }
    this._log.printErrorExit('THE_TYPE_OF_TARGET_DEVICEC_DOES_NOT_MATCH_THE_DEVICE_TYPE_CONFIGURED_BY_MODULE', [
      this.moduleModel.getName(),
      mustType,
      deviceTypes.join(),
    ]);
  }

  /**
   * 校验用户配置的compilePluginFile是否符合规范
   *
   * @private
   */
  private checkHvigorPluginFile() {
    const hvigorPluginFile = this.compilePluginFile;
    if (hvigorPluginFile === undefined) {
      return;
    }
    if (this.moduleModel.isHarModule() && !new HarExtendInfo(this.moduleModel, this.targetService.getBuildOption()).isObfuscatedHar()) {
      this._log._buildError("'compilePluginFile' won't work for an unobfuscated library.")._file(this.moduleModel.getProfilePath())._printWarn(this.moduleName);
    }
    if (!hvigorPluginFile.startsWith(`./${BuildDirConst.HVIGOR_COMPILE}/`)) {
      this._log
        ._buildError(`'compilePluginFile' must start with './${BuildDirConst.HVIGOR_COMPILE}/'.`)
        ._file(this.moduleModel.getProfilePath())
        ._printErrorAndExit();
    }
    const parentDir = path.resolve(this.moduleModel.getProjectDir(), BuildDirConst.HVIGOR_COMPILE);
    let completePath = path.resolve(this.moduleModel.getProjectDir(), hvigorPluginFile);
    if (!isSubPath(completePath, parentDir)) {
      this._log._buildError(`'compilePluginFile' is not under directory: '${parentDir}'.`)._file(this.moduleModel.getProfilePath())._printErrorAndExit();
    }
    this.checkSuffix(completePath);
    if (!fse.existsSync(completePath)) {
      completePath = `${completePath}.ts`;
      if (!fse.existsSync(completePath)) {
        this._log._buildError("'compilePluginFile' does not exist.")._file(this.moduleModel.getProfilePath())._printErrorAndExit();
      }
    }
  }

  /**
   * 校验文件路径后缀是否是.ts（如果后缀存在）
   *
   * @param hvigorPluginFile 待校验的路径
   * @private
   */
  private checkSuffix(hvigorPluginFile: string) {
    const pathList = hvigorPluginFile.split(path.sep);
    const len = pathList.length;
    if (len === 0) {
      return true;
    }
    const fileName = pathList[len - 1];
    const dotLoc = fileName.lastIndexOf('.');
    if (dotLoc > 0) {
      const suffix = fileName.substring(dotLoc);
      if (suffix !== '.ts') {
        this._log._buildError("'compilePluginFile' must end with .ts.")._file(this.moduleModel.getProfilePath())._printErrorAndExit();
      }
    }
    return true;
  }

  protected isPackingTestPackage(): boolean {
    return this.targetName === DefaultTargetConst.OHOS_TEST_TARGET && this.compileApiVersion >= ApiVersion.API_VERSION_8 && this.service.hasLiteDevice();
  }

  protected abstract doValidateForDiffApiType(): void;

  protected abstract getJsonProfileByModel(): JsonProfile;

  protected abstract checkExtendSourceDirs(): void;

  protected abstract validateAtomicService(): void;

  protected abstract checkByteCodeHar(): void;

  protected abstract doValidateForStaticArkTS(): void;

  protected supportOHPM() {
    return this.sdkInfo.supportOhpmProject(this.targetData.isHarmonyOS());
  }

  /**
   * 校验hsp/har ohPackage.json5中的main字段和types字段配置的文件是否存在
   */
  async checkPackageJson5Config() {
    if (!(this.moduleModel.getModuleType() === ModuleType.Har || this.moduleModel.getModuleType() === ModuleType.Shared)) {
      return;
    }
    const packageJson: PackageJsonOpt = await Json5Reader.readJson5File(this.packageManagerPath);
    const moduleDir: string = this.moduleModel.getProjectDir();
    this.checkMainAndTypesFile(packageJson.main, packageJson.types);
    if (packageJson.main && !fs.existsSync(path.resolve(moduleDir, packageJson.main))) {
      this._log.printErrorExit(
        'MAIN_FILE_DOES_NOT_EXIST',
        [packageJson.main, this.moduleModel.getOhPackageJson5Path()],
        [[packageJson.main]]
      );
    }
    if (packageJson.types && !fs.existsSync(path.resolve(moduleDir, packageJson.types))) {
      this._log.warn(`Invalid types file ${packageJson.types} defined in ${this.moduleModel.getName()} ${CommonConst.OH_PACKAGE_JSON5}.`);
    }

    // 判断是否属于相同模块
    if (packageJson.main) {
      const mainPath = path.resolve(moduleDir, packageJson.main);
      if (fs.existsSync(mainPath) && !mainPath.includes(moduleDir)) {
        this._log.warn(`Do not configure cross-module file ${packageJson.main} defined in ${this.moduleModel.getName()} ${CommonConst.OH_PACKAGE_JSON5}.`);
      }
    }
    if (packageJson.types) {
      const typesPath = path.resolve(moduleDir, packageJson.types);
      if (fs.existsSync(typesPath) && !typesPath.includes(moduleDir)) {
        this._log.warn(`Do not configure cross-module file ${packageJson.types} defined in ${this.moduleModel.getName()} ${CommonConst.OH_PACKAGE_JSON5}.`);
      }
    }
  }

  private checkMainAndTypesFile(mainFile: string | undefined, typesFile: string | undefined) {
    if (!mainFile && !typesFile) {
      this._log.warn(`Set either main or types, or both for this HSP/HAR module. at ${this.moduleModel.getName()} ${CommonConst.OH_PACKAGE_JSON5}.`);
    }
    if (mainFile) {
      const mainSuffix = FileUtil.getFileSuffix(mainFile);
      if (!(mainSuffix === 'ets' || mainSuffix === 'ts' || mainSuffix === 'js') || mainFile.endsWith('.d.ets') || mainFile.endsWith('.d.ts')) {
        this._log.printErrorExit(
          'INVALID_MAIN_FILE_EXT',
          [],
          [[this.moduleModel.getOhPackageJson5Path()]]
        );
      }
    }
    if (typesFile) {
      if (!(typesFile.endsWith('.d.ets') || typesFile.endsWith('.d.ts'))) {
        this._log.warn(`The value of types must be a .d.ts or .d.ets file. at ${this.moduleModel.getName()} ${CommonConst.OH_PACKAGE_JSON5}.`);
      }
    }
  }

  private checkWorkerConfig() {
    const workerConfig = this.targetService.getBuildOption()?.sourceOption?.workers;
    if (!workerConfig) {
      return;
    }

    const filePath = buildOptionPath.getTargetBuildOptPath(this.moduleModel, this.targetName, 'workers');

    // 配置的所有worker都要为相对路径、配置在当前模块下，且路径存在
    workerConfig.forEach((workerPath) => {
      if (path.isAbsolute(workerPath) && !FileUtil.fileExists(workerPath)) {
        this._log.printErrorExit(
          'WORKER_PATH_DOES_NOT_EXIST',
          [workerPath, filePath],
          [[workerPath], [workerPath]]
        );
      } else if (!FileUtil.fileExists(FileUtil.convertToAbsolutePath(workerPath, this.moduleModel.getProjectDir()))) {
        this._log.printErrorExit(
          'WORKER_PATH_DOES_NOT_EXIST',
          [workerPath, filePath],
          [[workerPath], [workerPath]]
        );
      }

      // 配置的所有worker必须以ets/ts结尾
      if (!['.js', '.ts', '.ets'].includes(path.extname(workerPath))) {
        this._log.printErrorExit(
          'INVALID_WORKER_FILE_EXT',
          [workerPath, filePath],
          [[workerPath]]
        );
      }
    });
  }

  protected versionCheck(): void {
    const versionCheckRes = ValidateUtil.apiCompatibleCheck(this.projectModel, this.targetData, this.moduleModel);
    const arkTSCheckRes = this.checkArkTSCompatibility();
    const errorHandler = versionCheckRes.suggestedVersion > arkTSCheckRes.suggestedVersion ? versionCheckRes.errorHandler : arkTSCheckRes.errorHandler;
    if (errorHandler) {
      errorHandler();
    }
  }

  /**
   * 校验har和hsp模块的版本号和名称是否符合规范
   * @private
   */
  ohPackageCheck() {
    const pkgJsonPath = this.projectModel.isOhpmProject()
      ? ohPackageLoader.getNodeOhPackagePath(path.resolve(this.moduleModel.getProjectDir(), CommonConst.OH_PACKAGE_JSON5))
      : this.moduleModel.getPackageJsonPath();
    if (fse.existsSync(pkgJsonPath)) {
      const pkgJsonObj = ohPackageLoader.getOhPackageJsonObj(pkgJsonPath);
      // 处理版本不符合semver规范
      const pkgJsonVersion = pkgJsonObj?.version;
      const regExpVersion = /^[1-9]\d?(\.([1-9]?\d)){2}$/;
      const pkgJsonVersionType = typeof pkgJsonVersion;
      if (pkgJsonVersionType !== 'string') {
        this._log
          .printErrorExit('OH_PACKAGE_VERSION_NOT_STRING', [pkgJsonPath, pkgJsonVersionType]);
      }
      if (pkgJsonVersion && !regExpVersion.test(pkgJsonVersion)) {
        this._log
          ._buildError(
            `The version number of the '${pkgJsonObj?.name}' module does not comply with the SemVer specification.
               This will affect the installation and publication by ohpm.
               Make sure the value of version in the oh-package.json5 file of the module complies with the SemVer specification.`,
          )
          ._printWarn(this.moduleName);
      }
      // 处理名称不符合命名规范
      const pkgJsonName = pkgJsonObj?.name;
      const regExpName = /^(@(?![0-9-_])[a-z0-9-_]+(?<![-_])\/)?(?![0-9-_.])[a-z0-9-_.]+(?<![-_.])$/;
      const pkgJsonNameType = typeof pkgJsonName;
      if (pkgJsonNameType !== 'string') {
        this._log
          .printErrorExit('OH_PACKAGE_NAME_NOT_STRING', [pkgJsonPath, pkgJsonNameType]);
      }
      if (pkgJsonName && !regExpName.test(pkgJsonName)) {
        this._log
          ._buildError(
            `The name of the '${pkgJsonObj?.name}' module does not comply with the regular expression: ${regExpName}.
               This will affect the installation and publication by ohpm.
               Make sure the value of name in the oh-package.json5 file of the module complies with the regular expression.`,
          )
          ._printWarn(this.moduleName);
      }
    }
  }

  /**
   * 校验编译语言是否支持wearable,只有API8的非js工程进行拦截
   * @private
   */
  private checkCodeTypeForWearable() {
    if (!this.deviceTypes.includes(DeviceTypeConst.WEARABLE)) {
      return;
    }
    if (this.containsEts && this.compatibleApiVersion === ApiVersion.API_VERSION_8) {
      this._log._buildError('Wearable devices for API version 8 support only JS.')._printErrorAndExit(this.moduleModel.getName());
    }
  }

  /**
   * 校验编译语言是否支持瘦设备lite-wearable、smartVision,对API8及API8以上的非js工程进行拦截
   * @private
   */
  private checkCodeTypeForLiteWearable() {
    if (
      (this.deviceTypes.includes(DeviceTypeConst.LITE_WEARABLE) || this.deviceTypes.includes(DeviceTypeConst.SMART_VISION)) &&
      this.containsEts &&
      this.compatibleApiVersion >= ApiVersion.API_VERSION_8
    ) {
      if (isSingleFramework()) {
        this._log
          .printErrorExit('LITE_WEARABLE_UNDER_API8_IN_SINGLE_FRAMEWORK_ONLY_SUPPORT_JS');
      } else {
        this._log._buildError('Lite-wearable devices with API version 8 or later support only JS.')._printErrorAndExit(this.moduleModel.getName());
      }
    }
  }

  /**
   * 校验SDK版本是否支持ohpm工程
   * @private
   */
  private checkOhpmProjectSdkVersion() {
    if (this.isOhpmProject && !this.supportOHPM()) {
      const apiVersion = this.sdkInfo.getSdkVersion();
      const runtimeOS = ProjectExtraInfoService.getProjectRuntimeOS(this.projectModel);
      const projectType = runtimeOS === RuntimeTypeEnum.HARMONY_OS ? 'SDK > HarmonyOS' : 'SDK > OpenHarmony';
      this._log.printErrorExit(
        'SDK_VERSION_TOO_LOW',
        [],
        [[apiVersion, projectType]]
      );
    }
  }

  /**
   * 校验API版本是否支持测试框架
   * @private
   */
  private checkApiVersionForUnitTest() {
    const isSupportUnitTest = InjectUtil.isUnitTestProcess();
    if (isSupportUnitTest && this.apiType === ApiType.FA) {
      this._log.printErrorExit('FA_MODE_DOES_NOT_SUPPORT_UNIT_TEST');
    }
  }

  /**
   * fastBuildApp cannot usage on FA Mode
   *
   */
  protected validateFastBuildAppOnFAMode(): void {
    if (this.projectModel.isFaMode() && findTargetProduct(this.projectModel)?.buildOption?.packOptions?.fastBuildApp) {
      this._log.printErrorExit('FA_MODE_DOES_NOT_SUPPORT_FAST_BUILD');
    }
  }

  protected validateModuleName(profileModuleName: string, moduleName: string, sourceConfigName: string) {
    if (profileModuleName !== moduleName) {
      this._log.printErrorExit(
        'MULTIPLE_MODULE_NAME',
        [profileModuleName, sourceConfigName, nodeConfigPathInfo.getNodeConfigPathByName(profileModuleName)],
        [[profileModuleName, moduleName]]
      );
    }
  }

  protected getBundleType() {
    const appOptFromConfig: AppOpt = cloneDeep(projectOhosConfigManager.getOverrides()?.appOpt);
    return appOptFromConfig?.bundleType ?? this.targetData.getProduct().bundleType ?? this.projectModel.getBundleType();
  }

  protected isAtomicServiceProject() {
    return this.getBundleType() === BundleType.ATOMIC_SERVICE;
  }

  private checkArkTSCompatibility(): VersionCheckRes {
    const etsCodeMode = this.moduleModel.getSourceSetByTargetName(this.targetData.getTargetName()).getCodeMap().get(CodeType.ETS);
    const minimumArkTSVersion = 7;
    if (etsCodeMode?.getSrcPath() && this.compatibleApiVersion < minimumArkTSVersion) {
      // api 7 以下无法兼容ets
      return {
        suggestedVersion: minimumArkTSVersion,
        errorHandler: () => {
          this._log
            ._buildError(`The minimum compatible version of ArkTS is ${minimumArkTSVersion}.`)
            ._solution(`Modify build-profile.json5 compatibleSdkVersion to ${minimumArkTSVersion} or later.`)
            ._file(this.projectModel.getProfilePath())
            ._printErrorAndExit();
        },
      };
    }
    return { suggestedVersion: -1 };
  }

  private checkCustomTypesSupport() {
    const customTypes = this.targetService.getCustomTypes();
    if (!customTypes || (Array.isArray(customTypes) && customTypes.length === 0)) {
      return;
    }
    if (this.compileApiVersion < ApiVersion.API_VERSION_10) {
      this._log
        ._buildError('Only API version 10 or later supports custom types.')
        ._detail(`Modify build-profile.json5 api version to ${ApiVersion.API_VERSION_10} or later.`)
        ._file(this.projectModel.getProfilePath())
        ._printErrorAndExit();
    }
    const etsCodeMode = this.moduleModel.getSourceSetByTargetName(this.targetData.getTargetName()).getCodeMap().get(CodeType.ETS);
    if (!etsCodeMode) {
      this._log.printErrorExit(
        'TYPES_WITHOUT_ETS_CODE',
        [],
        [[], [ApiVersion.API_VERSION_10]]
      );
    }
    this.checkCustomTypesIsValid(customTypes);
  }

  private checkCustomTypesIsValid(customTypes: string[]) {
    this.checkCustomTypeExtName(customTypes, this.dependencies);
    this.checkCustomTypePathIsValid(customTypes, this.dependencies);
  }

  private checkCustomTypePathIsValid(customTypes: string[], dependencies: Record<string, string>) {
    const pathErrorTypes: string[] = [];
    for (const type of customTypes) {
      // 1、校验包名
      if (dependencies[type] || dependencies[`@types/${type}`]) {
        continue;
      }

      // 2、校验路径
      if (getCustomTypePathIfExists(this.moduleModel.getProjectDir(), type)) {
        continue;
      }
      pathErrorTypes.push(type);
    }
    if (pathErrorTypes.length > 0) {
      this._log.printErrorExit(
        'TYPES_FILES_DO_NOT_EXIST',
        [pathErrorTypes.join(','), buildOptionPath.getTargetBuildOptPath(this.moduleModel, this.targetName, 'types')]
      );
    }
  }

  private checkCustomTypeExtName(customTypes: string[], dependencies: Record<string, string>) {
    for (const type of customTypes) {
      // 1、校验包名
      if (dependencies[type] || dependencies[`@types/${type}`]) {
        continue;
      }
      const extName = path.extname(type);
      if (extName === '') {
        continue;
      }
      const typePath = path.isAbsolute(type) ? type : path.resolve(this.moduleModel.getProjectDir(), type);
      if (!fs.existsSync(typePath)) {
        continue;
      }
      for (const suffix of CustomTypesConst.CUSTOM_TYPES_SUFFIX) {
        if (type.endsWith(suffix)) {
          this._log.printErrorExit(
            'TYPES_FILES_WITH_REDUNDANT_EXT',
            [type, suffix, buildOptionPath.getTargetBuildOptPath(this.moduleModel, this.targetName, 'types')],
            [[suffix, type]]
          );
        }
      }
      this._log.printErrorExit(
        'INVALID_TYPES_FILES_EXT',
        [type, buildOptionPath.getTargetBuildOptPath(this.moduleModel, this.targetName, 'types')]
      );
    }
  }

  private getModuleDynamicDependencies(): Record<string, string> {
    const pkgJsonPath = this.isOhpmProject ? this.moduleModel.getOhPackageJson5Path() : this.moduleModel.getPackageJsonPath();
    let dynamicDependencies: Record<string, string> = {};
    if (fs.existsSync(pkgJsonPath)) {
      const pkgJsonObj = Json5Reader.getJson5Obj(pkgJsonPath);
      dynamicDependencies = {
        dynamicDependencies,
        ...pkgJsonObj.dynamicDependencies,
      };
    }
    return dynamicDependencies;
  }

  private checkDynamicDependenciesSupport() {
    const length = Object.keys(this.dynamicDependencies).length;
    if (length > 1) {
      if (this.isFaMode || this.compileApiVersion < 9 || !this.containsEts) {
        this._log.printErrorExit(
          'DYNAMIC_DEPENDENCIES_PROHIBIT',
          [this.moduleName]
        );
      }
    }
  }

  private checkDimension(formsObj: ModuleJson.FormsObj, formConfigFilePath: string): void {
    const errorWidgetNames: string[] = [];
    formsObj?.forms?.forEach((item) => {
      if (!item.supportDimensions.includes(item.defaultDimension)) {
        errorWidgetNames.push(item.name);
      }
    });

    if (errorWidgetNames.length === 0) {
      return;
    }

    this._log.printErrorExit(
      'INVALID_DEFAULT_DIMENSION',
      [errorWidgetNames.join('","'), formConfigFilePath]
    );
  }

  /**
   * entryModules字段在stage模型工程中标识废弃 编辑器中无法通过schema体现 则在构建时抛出warning
   * @private
   */
  private checkEntryModules(): void {
    const moduleModel = this.service.getModuleModel();
    const entryModules = moduleModel.getProfileOpt().entryModules;
    const buildProfilePath = moduleModel.getProfilePath();
    if (this.apiType === ApiType.STAGE && entryModules) {
      this._log.warn(
        'In the stageMode project, the entryModules field configured in feature module is deprecated, ' +
          `but the compatibility of the old project is still maintained.
         You can configured it in ${buildProfilePath} .`,
      );
    }
  }

  /**
   * 校验配置项ohos.arkCompile.emptyBundleName/ohos.byteCodeHar.integratedOptimization是否为布尔值，如果不是需要给出warning
   * @private
   */
  private checkEmptyProperty(): void {
    const keys = [CommonConst.EMPTY_BUNDLENAME, HvigorConfigConst.OHOS_BYTE_CODE_HAR_OPTIMIZE];
    keys.forEach(key => {
      const value = hvigorCore.getParameter().getProperty(key);
      if (value && typeof value !== 'boolean'){
        this._log.warn(`The configuration ${key} needs to be a boolean. Current type '${typeof value}' is invalid and has been defaulted to false.`);
      }
    });
  }

  private getFromConfigFilePathArr(): string[] {
    const metadataResourceDirArr: string[] = [];
    const profileObj = this.jsonProfile.profile as ModuleOptObj;
    const extensionAbilities = profileObj.module.extensionAbilities;
    const metadataObjArr: (MetadataObj[] | undefined)[] | undefined = extensionAbilities?.map((obj) => obj.metadata);
    metadataObjArr?.forEach((metadataObj) => {
      const resourceDirs = metadataObj?.map((obj) => obj.resource);
      resourceDirs?.forEach((item) => (item ? metadataResourceDirArr.push(item!) : undefined));
    });

    const formConfigFilePathArr: string[] = [];
    metadataResourceDirArr.forEach((resource) => {
      const formConfigFilePath = this.pathInfo.getFromConfigPathByTargetName(this.moduleModel, this.targetName, parsingProfileName(resource));
      if (!formConfigFilePath || !fse.existsSync(formConfigFilePath)) {
        return;
      }
      formConfigFilePathArr.push(formConfigFilePath);
    });

    return formConfigFilePathArr;
  }

  private checkWidget(): void {
    if (this.isFaMode || this.targetData.getCompileApiVersion() < ApiVersion.API_VERSION_9 || this.targetName === DefaultTargetConst.OHOS_TEST_TARGET) {
      return;
    }

    this.fromConfigFilePathArr.forEach((formConfigFilePath) => {
      const formConfigObj = getJson5Obj(formConfigFilePath, 'utf-8') as ModuleJson.FormsObj;
      this.checkDimension(formConfigObj, formConfigFilePath);
    });
  }

  private checkRequestPermissions(): void {
    if (!this.isFaMode && !(this.targetName === DefaultTargetConst.OHOS_TEST_TARGET)) {
      this.checkLegalReqPermissions(this.jsonProfile.profile);
    }
    const noUniqueValues: string[] = this.findDuplicatedPermissions(this.jsonProfile.profile);
    if (noUniqueValues.length === 0) {
      return;
    }

    this._log.printErrorExit(
      'DUPLICATED_PERMISSIONS',
      [this.isFaMode ? 'reqPermissions' : 'requestPermissions', noUniqueValues.join('","'), this.jsonProfile.jsonFilePath]
    );
  }

  /**
   * 用于检查build.profile.json5中removePermissions字段：
   * 所配置的权限是否被系统所定义，若否则提供警告
   * @private
   */
  private checkRemoveRequestPermissions(): void {
    if (!this.isFaMode && !(this.targetName === DefaultTargetConst.OHOS_TEST_TARGET)) {
      const permissions: RequestPermissionObj[] | undefined = this.targetService.getBuildOption().removePermissions;
      const definePermissions: ConfigJson.DefPermissionsObj[] = this.jsonProfile.profile.module.definePermissions || [];
      if (permissions !== undefined && permissions.length > 0 && (this.moduleModel.isHapModule() || this.moduleModel.isHspModule())) {
        const allPermission = this.getGrantData(definePermissions).allPermission;
        this.forEachCheckRemovePermissions(permissions, allPermission);
      }
    }
  }

  /**
   * 检查build.profile.json5中removePermissions字段中的每个权限
   * 提供具体告警信息
   * @param permissions removePermissions字段中的每个权限
   * @param allPermission 系统支持的权限字段
   * @private
   */
  private forEachCheckRemovePermissions(permissions: ReqPermissionsObj[] | RequestPermissionObj[], allPermission: string[]) {
    if (allPermission.length === 0) {
      this._log.warn(`No usable permissions predefined within the SDK or a custom one that you have included under definePermissions.`);
      return;
    }
    for (const permission of permissions) {
      if (!allPermission.includes(permission.name)) {
        this._log.warn(
          `The ${
            permission.name
          } permission under removePermissions must be a value that is predefined within the SDK or a custom one that you have included under definePermissions.
at ${buildOptionPath.getTargetBuildOptPath(this.moduleModel, this.targetName, 'removePermissions')}`,
        );
      }
    }
    return;
  }

  private findDuplicatedPermissions(profile: ModuleOptObj | ConfigOptObj): string[] {
    const uniqueValues: string[] = [];
    const noUniqueValues: string[] = [];
    const permissions: ReqPermissionsObj[] | RequestPermissionObj[] | undefined = this.isFaMode
      ? (profile as ConfigOptObj).module.reqPermissions
      : (profile as ModuleOptObj).module.requestPermissions;
    permissions?.forEach((item) => {
      if (uniqueValues.includes(item.name) && !noUniqueValues.includes(item.name)) {
        noUniqueValues.push(item.name);
      } else {
        uniqueValues.push(item.name);
      }
    });

    return noUniqueValues;
  }

  private checkLegalReqPermissions(profile: ModuleOptObj | ConfigOptObj) {
    const permissions: ReqPermissionsObj[] | RequestPermissionObj[] | undefined = this.isFaMode
      ? (profile as ConfigOptObj).module.reqPermissions
      : (profile as ModuleOptObj).module.requestPermissions;
    const definePermissions: ConfigJson.DefPermissionsObj[] = profile.module.definePermissions || [];
    if (permissions !== undefined && permissions.length > 0) {
      this.checkReqPermissionsByModuleType(permissions, definePermissions);
    }
  }

  /**
   * 根据模块类别对user_grant requestPermission进行校验
   * 如果是hap，校验user_grant 权限的的 reason 和 usedScene 必填
   * 如果是har和hsp，校验user_grant 权限的 reason 为必填
   *
   * @param permissions
   * @param definePermissions
   * @private
   */
  private checkReqPermissionsByModuleType(permissions: ReqPermissionsObj[] | RequestPermissionObj[], definePermissions: ConfigJson.DefPermissionsObj[]) {
    const { userGrantData, allPermission } = this.getGrantData(definePermissions);
    if (this.moduleModel.isHapModule()) {
      this.forEachCheckPermissions(permissions, allPermission, userGrantData, true);
    }
    if (this.moduleModel.isHspModule() || this.moduleModel.isHarModule()) {
      this.forEachCheckPermissions(permissions, allPermission, userGrantData, false);
    }
  }

  /**
   * 组织权限数据，取SDK配置权限以及definePermissions配置的权限
   * runtimeOS为openHarmony且compileSdkVersion<12，则沿用老的逻辑权限列表从hvigor中取
   * @param definePermissions
   * @private
   */
  private getGrantData(definePermissions: ConfigJson.DefPermissionsObj[]) {
    let userGrantSet: Set<string>;
    let allPermission: string[] = [];
    if (this.isUseSdkPermission()) {
      userGrantSet = new Set(this.sdkInfo.getPreviewerUserGrant());
      allPermission = this.sdkInfo.getPreviewerPermissionNames();
    } else {
      const userGrant = JsonReader.getJsonObj(path.resolve(__dirname, '../../../res/permissions/userGrantPermissions.json'), 'utf-8').userGrantPermissions;
      userGrantSet = new Set(userGrant);
    }

    const defineSet: Set<string> = new Set();
    definePermissions?.forEach((permission: ConfigJson.DefPermissionsObj) => {
      // 重名取第一个
      if (!permission.name || defineSet.has(permission.name)) {
        return;
      }
      if (permission.grantMode !== 'user_grant' && userGrantSet.has(permission.name)) {
        userGrantSet.delete(permission.name);
      } else if (permission.grantMode === 'user_grant') {
        userGrantSet.add(permission.name);
      }
      allPermission.push(permission.name);
      defineSet.add(permission.name);
    });

    return {
      userGrantData: Array.from(userGrantSet),
      allPermission,
    };
  }

  private forEachCheckPermissions(permissions: ReqPermissionsObj[] | RequestPermissionObj[], allPermission: string[], userGrantData: string[], isHap: boolean) {
    const isUseSdkPermission = this.isUseSdkPermission();
    for (const permission of permissions) {
      if (isUseSdkPermission && !allPermission.includes(permission.name)) {
        this._log.printErrorExit('THE_%S_PERMISSION_UNDER_REQUESTPERMISSIONS+MUST_BE_A_VALUE', [permission.name, this.jsonProfile.jsonFilePath]);
        return;
      }
      if (!userGrantData.includes(permission.name)) {
        continue;
      }
      if (isHap && (permission.reason === undefined || permission.usedScene === undefined)) {
        this._log.printErrorExit('THE_REASON_AND_USESCENE_ATTRIBUTES_ARE_MANDATORY_FOR_USER_GRANT_PERMISSIONS', [this.jsonProfile.jsonFilePath]);
      }
      if (!isHap && permission.reason === undefined) {
        this._log.printErrorExit('THE_REASON_ATTRIBUTE_ARE_MANDATORY_FOR_USER_GRANT_PERMISSIONS', [this.jsonProfile.jsonFilePath]);
      }
    }
  }

  /**
   * 是否需要使用SDK配置权限
   * api版本大于12或者未使用OpenHarmonyOS
   *
   * @return boolean
   */
  private isUseSdkPermission(): boolean {
    return this.sdkInfo.allowSdkPermission() && (this.compileApiVersion >= ApiVersion.API_VERSION_12 || this.targetData.isHarmonyOS());
  }

  protected isValidWidget(isArkTsWidget: boolean, parentDir: string, formPath: string): boolean {
    if (isArkTsWidget) {
      return FileUtil.fileExists(formPath);
    }

    // js widget may exist in js or supervisual dir
    return FileUtil.fileExists(`${formPath}.hml`) || this.visualExists(`${formPath}.visual`);
  }

  private visualExists(formPath: string): boolean {
    const superVisualPath = formPath.replace(`${path.sep}js${path.sep}`, `${path.sep}supervisual${path.sep}`);
    return FileUtil.fileExists(superVisualPath);
  }

  /**
   * 检查excludeSoFromInterfaceHar是否配置到打hap的地方，是则需要报warning
   * excludeSoFromInterfaceHar只适用于打hsp包的变量，
   *
   */
  private checkExcludeSoFromInterfaceHar() {
    const excludeSoFromInterfaceHar = this.targetService.getBuildOption().nativeLib?.excludeSoFromInterfaceHar;

    if (excludeSoFromInterfaceHar && this.moduleModel.isHapModule()) {
      this._log.warn(`excludeSoFromInterfaceHar only applies to HSPs. It is not effective for HAPs.
              Check your nativeLib configuration in ${path.resolve(this.pathInfo.getModulePath(), 'build-profile.json5')}.`);
    }
  }
}
